//! Utils for supporting a group emitting application-layer messages.

use openmls::framing::MlsMessageOut;
use openmls::group::GroupId;

use aspect_db_common::{
    async_mq::QueueId,
    group::{
        traits::GroupOutputDb,
        types::{AppMsg, MsgEntry},
    },
};
use aspect_db_llmq::LlmqDatabase;

use super::errors::InfraError;

/// Trait used to write messages to the group's message log.
pub trait GroupOutput {
    /// Writes a standard application message.
    fn write_msg(&self, msg: AppMsg) -> Result<(), InfraError>;
}

/// Group output that writes to a database implementing the standard trait.
pub struct DbGroupOutput<D: GroupOutputDb> {
    group_id: GroupId,
    db: D,
}

impl<D: GroupOutputDb> DbGroupOutput<D> {
    pub fn new(group_id: GroupId, db: D) -> Self {
        Self { group_id, db }
    }
}

impl<D: GroupOutputDb> GroupOutput for DbGroupOutput<D> {
    fn write_msg(&self, msg: AppMsg) -> Result<(), InfraError> {
        self.db
            .write_next_entry(&self.group_id, MsgEntry::App(msg))?;
        Ok(())
    }
}

/// Broadcast trait used to send messages to the delivery service.
pub trait GroupBroadcast {
    /// Broadcasts an MLS message out to the delivery service.
    fn broadcast_mls_out(&self, out_msg: MlsMessageOut) -> Result<(), InfraError>;
}

/// Stores the broadcasted messages in an LLMQ database.
// TODO have some notification system so we can trigger the outbound code
// to do something?
pub struct MqBroadcastStore {
    queue_id: QueueId,
    mqds: LlmqDatabase,
}

impl MqBroadcastStore {
    pub fn new(queue_id: QueueId, mqds: LlmqDatabase) -> Self {
        Self { queue_id, mqds }
    }
}

impl GroupBroadcast for MqBroadcastStore {
    fn broadcast_mls_out(&self, out_msg: MlsMessageOut) -> Result<(), InfraError> {
        let raw = out_msg.to_bytes()?;
        self.mqds.submit_generic_msg(&self.queue_id, raw)?;
        Ok(())
    }
}
